using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace Framework
{
    [AsyncMethodBuilder(typeof(AsyncFTaskMethodBuilder))]
    public partial struct FTask : IEquatable<FTask>
    {
        private readonly IAwaiter m_Awaiter;

        public FTask(IAwaiter awaiter)
        {
            this.m_Awaiter = awaiter;
        }

        public AwaiterStatus Status => this.m_Awaiter?.Status ?? AwaiterStatus.Succeeded;

        public bool IsCompleted => this.m_Awaiter?.IsCompleted ?? true;

        public void GetResult()
        {
            if (this.m_Awaiter != null)
            {
                this.m_Awaiter.GetResult();
            }
        }

        public void Coroutine()
        {
        }

        public Awaiter GetAwaiter()
        {
            return new Awaiter(this);
        }

        public bool Equals(FTask other)
        {
            if (this.m_Awaiter == null && other.m_Awaiter == null)
            {
                return true;
            }
            if (this.m_Awaiter != null && other.m_Awaiter != null)
            {
                return this.m_Awaiter == other.m_Awaiter;
            }
            return false;
        }

        public override int GetHashCode()
        {
            if (this.m_Awaiter == null)
            {
                return 0;
            }
            return this.m_Awaiter.GetHashCode();
        }

        public override string ToString()
        {
            return this.m_Awaiter == null ?  "()" 
                : this.m_Awaiter.Status == AwaiterStatus.Succeeded ? "()"
                : "(" + this.m_Awaiter.Status + ")";
        }

        public struct Awaiter : IAwaiter
        {
            private readonly FTask m_Task;

            public Awaiter(FTask task)
            {
                this.m_Task = task;
            }

            public bool IsCompleted => this.m_Task.IsCompleted;

            public AwaiterStatus Status => this.m_Task.Status;

            public void GetResult()
            {
                this.m_Task.GetResult();
            }

            public void OnCompleted(Action continuation)
            {
                if (this.m_Task.m_Awaiter != null)
                {
                    this.m_Task.m_Awaiter.OnCompleted(continuation);
                }
                else
                {
                    continuation();
                }
            }

            public void UnsafeOnCompleted(Action continuation)
            {
                if (this.m_Task.m_Awaiter != null)
                {
                    this.m_Task.m_Awaiter.UnsafeOnCompleted(continuation);
                }
                else
                {
                    continuation();
                }
            }
        }
    }

    [AsyncMethodBuilder(typeof(AsyncFTaskMethodBuilder<>))]
    public struct FTask<T> : IEquatable<FTask<T>>
    {
        private readonly T m_Result;
        private readonly IAwaiter<T> m_Awaiter;

        public FTask(IAwaiter<T> awaiter)
        {
            this.m_Result = default;
            this.m_Awaiter = awaiter;
        }

        public FTask(T result)
        {
            this.m_Result = result;
            this.m_Awaiter = null;
        }

        public AwaiterStatus Status => this.m_Awaiter?.Status ?? AwaiterStatus.Succeeded;

        public bool IsCompleted => this.m_Awaiter?.IsCompleted ?? true;

        public T Result
        {
            get
            {
                if (this.m_Awaiter == null)
                {
                    return this.m_Result;
                }
                return this.m_Awaiter.GetResult();
            }
        }

        public void Coroutine()
        {
        }

        public Awaiter GetAwaiter()
        {
            return new Awaiter(this);
        }

        public bool Equals(FTask<T> other)
        {
            if (this.m_Awaiter == null && other.m_Awaiter == null)
            {
                return EqualityComparer<T>.Default.Equals(this.m_Result, other.m_Result);
            }
            if (this.m_Awaiter != null && other.m_Awaiter != null)
            {
                return this.m_Awaiter == other.m_Awaiter;
            }
            return false;
        }

        public override int GetHashCode()
        {
            if (this.m_Awaiter == null)
            {
                if (this.m_Result == null)
                {
                    return 0;
                }
                return this.m_Result.GetHashCode();
            }
            return this.m_Awaiter.GetHashCode();
        }

        public override string ToString()
        {
            return this.m_Awaiter == null ? this.m_Result.ToString()
                    : this.m_Awaiter.Status == AwaiterStatus.Succeeded ? this.m_Awaiter.GetResult().ToString()
                    : "(" + this.m_Awaiter.Status + ")";
        }

        public static implicit operator FTask(FTask<T> task)
        {
            if (task.m_Awaiter != null)
            {
                return new FTask(task.m_Awaiter);
            }
            return new FTask();
        }

        public struct Awaiter : IAwaiter<T>
        {
            private readonly FTask<T> m_Task;

            public Awaiter(FTask<T> task)
            {
                this.m_Task = task;
            }

            public bool IsCompleted => this.m_Task.IsCompleted;

            public AwaiterStatus Status => this.m_Task.Status;

            void IAwaiter.GetResult()
            {
                this.GetResult();
            }

            public T GetResult()
            {
                return this.m_Task.Result;
            }

            public void OnCompleted(Action continuation)
            {
                if (this.m_Task.m_Awaiter != null)
                {
                    this.m_Task.m_Awaiter.OnCompleted(continuation);
                }
                else
                {
                    continuation();
                }
            }

            public void UnsafeOnCompleted(Action continuation)
            {
                if (this.m_Task.m_Awaiter != null)
                {
                    this.m_Task.m_Awaiter.UnsafeOnCompleted(continuation);
                }
                else
                {
                    continuation();
                }
            }
        }
    }
}